package pro.cherish.knowledgeormmysql.config.mybatis.intercepter;

/**
 * @author 创建人：cherish
 * @version 版本号：V1.0
 * <p>
 * ***************************修订记录************************************
 * 2019-10-11 cherish 创建该类功能。
 * **********************************************************************
 * </p>
 * @ClassName 类名：PrepareInterceptor
 * @Description 功能说明：
 * <p>
 * TODO
 * </p>
 * **********************************************************************
 * @date 创建日期： 2019-10-11 cherish
 */


import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectItem;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import pro.cherish.knowledgeormmysql.config.mybatis.intercepter.annotation.EtlDynamicSql;
import pro.cherish.knowledgeutil.str.StringUtilExt;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 创建人：cherish
 * @version 版本号：V1.0
 * <p>
 * ***************************修订记录************************************
 * 2019-10-11 cherish 创建该类功能。
 * **********************************************************************
 * </p>
 * @ClassName 类名：PrepareInterceptor
 * @Description 功能说明：Mybatis拦截器，拦截查询语句，对有EtlDynamicSql标注的mapper方法进行处理：
 * 将mybatis模板传递mybatis处理后【并支持分页】，将最后构建的可执行sql传递到线程变量中，供后续etl获取并执行
 * <p>
 * TODO
 * </p>
 * **********************************************************************
 * @date 创建日期： 2019-10-11 cherish
 */
@Intercepts({
        @Signature(method = "query", type = Executor.class, args = {
                MappedStatement.class, Object.class, RowBounds.class,
                ResultHandler.class})})
@Slf4j
public class PrepareInterceptor implements Interceptor {

    /**
     * @Description: 拦截
     * @Param: [invocation]
     * @return: java.lang.Object
     * @Author: cherish
     * @Date: 2019/10/11
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (invocation.getMethod().getName().equals("query")) {
            Object[] args = invocation.getArgs();
            // 特殊处理
            String mapperStamentId = ((MappedStatement) invocation.getArgs()[0]).getId();
            String[] split = mapperStamentId.split("\\.");
            String classStr = split[split.length - 2];
            String methodStr = split[split.length - 1];
            String classPath = StringUtils.substringBefore(mapperStamentId, "." + methodStr);
            Class<?> clazz = Class.forName(classPath);
            List<Method> methodList = Arrays.stream(clazz.getMethods()).filter(method -> method.getName().equals(methodStr)).collect(Collectors.toList());
            Method targetMethod = methodList.get(0);
            Annotation[] annotations = targetMethod.getAnnotations();
            boolean isEtlDynamic = Arrays.stream(annotations).anyMatch(annotation -> annotation.annotationType().equals(EtlDynamicSql.class));
            if (isEtlDynamic) {
                MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
                Object parameter = null;
                if (invocation.getArgs().length > 1) {
                    parameter = invocation.getArgs()[1];
                }
                BoundSql boundSql = mappedStatement.getBoundSql(parameter);
                Configuration configuration = mappedStatement.getConfiguration();
                //获取sql语句
                String sql = getSql(configuration, boundSql);
                log.info("【myBatis拦截器PrepareInterceptor】获取SQL:{}", sql);
                Map<String, Object> paramMap = (HashMap) ((MapperMethod.ParamMap) args[1]).get("param2");
                Object exportWay = paramMap.get("exportWay");
                if (exportWay != null && StringUtilExt.isNotBlank(exportWay.toString()) && exportWay.toString().toUpperCase().equals(ExportWayEnum.PAGE.getCode())) {
                    Integer pageNo = Integer.valueOf(paramMap.get("pageNo").toString());
                    Integer pageSize = Integer.valueOf(paramMap.get("pageSize").toString());
                    Page page = new Page(pageNo, pageSize);
                    String paginationSql = buildPageSql(sql, page);
                    log.info("【myBatis拦截器PrepareInterceptor】分页改造后，sql: {}", paginationSql);
                    log.info("分页改造后的sql放入线程【{}】中，具体sql: {}", Thread.currentThread().getName(), paginationSql);
                    DynamicSqlStore.realSqlMap.set(paginationSql);
                } else {
                    /**
                     * 兼容分页与etl图需要row_id
                     * version1
                     *  sb.append("select t.*,t.rowid row_id from (");
                     *  如果内部sql不存在row_id好像会出问题。【具体未细跟】
                     * fixme 使用同一张ktr图，其中一个step中将ROW_ID列采用空串替换，所以如下冗余只是为了不报错。
                     * version2
                     * 使用rownum 重命名为row_id
                     */
                    StringBuilder sb = new StringBuilder();
                    sb.append("select t.*,rownum row_id from (");
                    sb.append(sql);
                    sb.append(") t");
                    sql = sb.toString();
                    log.info("改造后的sql放入线程【{}】中，具体sql: {}", Thread.currentThread().getName(), sql);
                    DynamicSqlStore.realSqlMap.set(sql);
                }
                return Collections.emptyList();
            } else {
                return invocation.proceed();
            }
        } else {
            return invocation.proceed();
        }
    }


    private String buildPageSql(String sql, Page page) {
        StringBuilder stringBuilder = buildPageSqlForOracle(sql, page);
        return stringBuilder.toString();
    }


    /**
     * 参考hibernate的实现完成oracle的分页
     *
     * @param sql  参数
     * @param page 参数
     * @return String
     */
    public StringBuilder buildPageSqlForOracle(String sql, Page page) {
        StringBuilder pageSql = new StringBuilder(100);
        String beginrow = String.valueOf((page.getCurrent() - 1) * page.getSize());
        String endrow = String.valueOf(page.getCurrent() * page.getSize());
        // 去除分页携带的rowid
        String columnsStr = remoreRowid(sql);

        pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
        pageSql.append(sql);
        pageSql.append(" ) temp where rownum <= ").append(endrow);
        pageSql.append(") where row_id > ").append(beginrow);
        return pageSql;
    }

    private String remoreRowid(String sql) {
        Select select = null;
        try {
            select = (Select) CCJSqlParserUtil.parse(sql);
        } catch (JSQLParserException e) {
            e.printStackTrace();
        }
        PlainSelect selectBody = (PlainSelect) select.getSelectBody();
        List<SelectItem> selectItems = selectBody.getSelectItems();

        return null;
    }

    /**
     * @Description: 获取最终可执行sql
     * @Param: [configuration, boundSql]
     * @return: java.lang.String
     * @Author: cherish
     * @Date: 2019/10/11
     */
    private String getSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterObject == null || parameterMappings.size() == 0) {
            return sql;
        }
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
        } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            for (ParameterMapping parameterMapping : parameterMappings) {
                String propertyName = parameterMapping.getProperty();
                if (metaObject.hasGetter(propertyName)) {
                    Object obj = metaObject.getValue(propertyName);
                    sql = sql.replaceFirst("\\?", getParameterValue(obj));
                } else if (boundSql.hasAdditionalParameter(propertyName)) {
                    Object obj = boundSql.getAdditionalParameter(propertyName);
                    sql = sql.replaceFirst("\\?", getParameterValue(obj));
                }
            }
        }
        return sql;
    }

    /**
     * @Description: 获取参数
     * @Param: [obj]
     * @return: java.lang.String
     * @Author: cherish
     * @Date: 2019/10/11
     */
    private String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }
        }
        return value;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}